<?php
/**
 *  The properties are stored in the __dict__:
 *  - to get a model's property, simple offsetGet() is OK
 *  - to iterate over properties, iterate over the model
 */
abstract class Yz_Model_ActiveRecord
    extends Yz_Model_Abstract
{

    /** The class name of model's items
     *  @var string
     */
    protected $itemClass;

    /** The list of model's items
     *  @var array
     */
    protected $items;
    #protected $items = array();

    /** The name of the source used by the model
     *  @var string|null
     */
    protected $sourceName;

    /** The parent model, if any
     *  @var Yz_Model_Interface|null
     */
    protected $parentModel;


    /****************************
     *      Public methods      *
     ****************************/

    /** get Model's parent model
     *  @return string
     */
    final public function getParentModel()
    {
        debug();
        return $this->parentModel;
    }


    /** get the associated item class
     *  @return string class(Yz_Item_Interface)
     */
    public function getItemClass()
    {
        debug();
        return $this->itemClass;
    }


    /** get the associated item class
     *  @return string class(Yz_Item_Interface)
     */
    public function getItems()
    {
        debug();
        return $this->items;
    }


    /** Create new item
     *  @return Yz_Item_Interface
     */
    public function item()
    {
        debug();
        return new $this->itemClass($this);
    }


    /** fetch item from the storage source
     *      Parameters:
     *  @param Yz_Item_Interface|Yz_List args
     *  @return Yz_Array{Yz_Item_Interface}
     */
    public function fetchListByKey($keyName, $args)
    {
        debug();
        if (! is_string($keyName))
            throw TypeError::_new_();
        // 1. identify which foreign key references the item
        if (! isset($this->__dict__[$keyName]))
            throw KeyError::_new_(array('key' => $keyName));

        // TODO add model name if key belongs to parent model
        $keyName = $this->name . '.' . $keyName . 'Id';
        // 2. identify the key value[s]
        if ($args instanceof Yz_Item_Interface) {
            assert('$args->getId() > 0');
            $keys = array($args->getId());
        } else if ($args instanceof Yz_List) {
            $keys = array_keys($keys->toArray());
        } else {
            throw TypeError::_new_();
        }
        $action = $this->selectAction('SelectListByKey');
        $values = $action
            ->setKey($keyName, $keys)
            ->perform()
            ->fetch()
        ;
        return $this->assignList($values);
    }


    /********************************
     *      Item internal methods   *
     ********************************/


    /** Delete item from the storage source
     *  NOTE: Designed to be called internally by item
     *      Do not use this method; use Item's delete()
     *  @todo check constraint for number of items
     *  @param Yz_Item_Interface
     *  @return this
     */
    public function deleteItem(Yz_Item_Interface $item)
    {
        debug();
        assert('$item instanceof $this->itemClass and isset($this->items[$item->getId()])');
        $action = $this
            ->selectAction('DeleteItem')
            ->setItem($item)
            ->perform()
        ;
        unset($this->items[$item->id]);
        return $this;
    }

    /** Add item to the storage source
     *  NOTE: Designed to be called internally by item
     *      Do not use this method; use Item's save()
     *  @todo check constraint for number of items
     *  @param Yz_Item_Interface
     *  @return this
     */
    public function insertItem(Yz_Item_Interface $item)
    {
        debug();
        assert('$item instanceof $this->itemClass');
        $action = $this
            ->selectAction('InsertItem')
            ->setItem($item)
            ->perform()
        ;
        $id = $item->getId();
        if (! $id) {
            $id = $action->getInsertId();
            assert('is_int($id) and $id > 0');
        }
        return $id;
    }

    /** Save item to the storage source
     *  NOTE: Designed to be called internally by item
     *      Do not use this method; use Item's save()
     *  @todo check constraint for number of items
     *  @param Yz_Item_Interface
     *  @return this
     */
    public function updateItem(Yz_Item_Interface $item)
    {
        debug();
        assert('$item instanceof $this->itemClass and isset($this->items[$item->id])');
        $action = $this
            ->selectAction('UpdateItem')
            ->setItem($item)
            ->perform()
        ;
        return $this;
    }


    /********************************
     *      Protected methods       *
     ********************************/



    /** Model's constructor
     *      Add item class
     */
    protected function __construct()
    {
        parent::__construct();
        $this->itemClass = $this->module->resolveClass('Item_' . $this->name, 'Yz_Item_ActiveRecord');
        $listClass = $this->module->resolveClass('Item_List_' . $this->name, 'Yz_Item_List');
        $this->items = new $listClass($this);
    }

    /**
     *
     */
    protected function extend()
    {
        $parentClass = get_parent_class($this);
        debug("Extending $parentClass");
        $parentModel = $parentClass::getInstance();
        //$parentModel->__dict__['_child' . $this->name]
        $this->__dict__[$parentModel->name] = new Yz_Model_Field_ParentModel($parentModel);
        $this->__dict__ += $parentModel->__dict__;
        $this->parentModel = $parentModel;
    }


}
